1944年,在第二次世界大戰中,同盟國聯軍進攻歐洲北部-法國諾曼第地區的登陸戰役,代號Operation Overlord。這次作戰由美國陸軍上將杜懷特‧艾森豪(Dwight D. Eisenhower)擔任最高指揮官,,作戰時間原訂為6月5日,但惡劣氣候迫使登陸行動推遲至6月6日。這是歷史上最著名的D-Day,也就是諾曼第登陸。
假設你現在是這場作戰總指揮官,你必須先設定計畫(Planning is everything. Plans are nothing!) - 也就是部隊要執行的命令。
但是你不太可能鉅細靡遺的下命令去指揮所有部隊 (如下圖一):
比較好的做法如下圖(圖二)
接下來我們開始用命令模式(Command)來實作這個需求。
將請求封裝為物件,允許使用不同的請求來參數化客戶端,駐列或記錄這些請求,並支持可撤銷的操作 ((WIKI)[https://en.wikipedia.org/wiki/Command_pattern])
命令模式的UML有點複雜,我們用這個登陸作戰需求來說明各角色的職責和關係(圖三):
Client: 總指揮官,負責
Command: 命令
Receiver: 部隊指揮官
Invoker: 作戰計畫,提供
我們開始來實作吧!
我們定義部隊的指揮官為三種:
他們各自實作了IReceiver
的細節,也就是各種部隊作戰的細節。
public interface IReceiver
{
/// 集合部隊
void GatherArmy();
/// 開火
void Fire();
/// 設定制高點=有利之位置
void SetHighGround();
/// 等待開火指示
void Hold();
/// 支援
void Support();
}
public class ReceiverArmy : IReceiver
{
public void Fire()
{
Trace.WriteLine("[Army] 坦克及路面部隊開始前進突破敵方防線!");
}
public void GatherArmy()
{
Trace.WriteLine("[Army] 集合裝甲部隊和部兵!");
}
public void Hold()
{
Trace.WriteLine("[Army] 子彈上膛!等待開火指令!");
}
public void SetHighGround()
{
Trace.WriteLine("[Army] 不要跑到有沙的地方!");
}
public void Support()
{
Trace.WriteLine("[Army] 以50機槍掃射掩護!");
}
}
//ReceiverNavy和ReceiverAirForce類別請參考ReceiverArmy自行建立或參考Github之原始碼
from abc import ABC, abstractmethod
class Receiver(ABC):
@abstractmethod
def gatherArmy(self):
"""集合部隊"""
pass
@abstractmethod
def fire(self):
"""開火"""
pass
@abstractmethod
def setHighGround(self):
"""設定制高點=有利之位置"""
pass
@abstractmethod
def hold(self):
"""等待開火指示"""
pass
@abstractmethod
def support(self):
"""支援"""
pass
class ReceiverAirForce(Receiver):
def fire(self):
print('[AirForce] 自由開火!')
def gatherArmy(self):
print('[AirForce] 集合戰鬥機飛官!')
def hold(self):
print('[AirForce] 組織巡邏隊形進行觀察!')
def setHighGround(self):
print('[AirForce] 飛高高!')
def support(self):
print('[AirForce] 以機槍掃射掩護!')
# ReceiverNavy和ReceiverAirForce類別請參考ReceiverArmy自行建立或參考Github之原始碼
每個命令必須有一個部隊(Receiver)來執行細節。
這裡我們讓總指揮官的命令包含:
public abstract class Command
{
protected IReceiver _receiver = null;
public Command(IReceiver receiver)
{
this._receiver = receiver;
}
public abstract void Execute();
}
///Breakthrough:突破
public class CmdBreakthrough:Command
{
public CmdBreakthrough(IReceiver receiver):base(receiver)
{
}
public override void Execute()
{
this._receiver.GatherArmy();
this._receiver.Fire();
}
}
///Defense: 防禦
public class CmdDefense : Command
{
public CmdDefense(IReceiver receiver):base(receiver)
{
}
public override void Execute()
{
this._receiver.SetHighGround();
this._receiver.Hold();
}
}
///Support: 支援
public class CmdSupport : Command
{
public CmdSupport(IReceiver receiver):base(receiver)
{
}
public override void Execute()
{
this._receiver.Support();
}
}
from abc import ABC, abstractmethod
from Receivers import Receiver
class Command(ABC):
def __init__(self, receiver:Receiver):
self.receiver = receiver
@abstractmethod
def execute(self):
pass
class CmdSupport(Command):
"""Support: 支援"""
def __init__(self, receiver:Receiver):
super().__init__(receiver)
def execute(self):
self.receiver.support()
class CmdDefense(Command):
"""Defense: 防禦"""
def __init__(self, receiver:Receiver):
super().__init__(receiver)
def execute(self):
self.receiver.setHighGround()
self.receiver.hold()
class CmdBreakthrough(Command):
"""Breakthrough: 突破"""
def __init__(self, receiver:Receiver):
super().__init__(receiver)
def execute(self):
self.receiver.gatherArmy()
self.receiver.fire()
public class Invoker
{
private IList<Command> _commands = null;
public Invoker()
{
this._commands = new List<Command>();
}
public void AddCommand(Command command)
{
this._commands.Add(command);
}
public void CancelCommand(Command command)
{
this._commands.Remove(command);
}
public void Invoke()
{
foreach(var cmd in this._commands)
{
cmd.Execute();
}
}
}
from Commands import Command
class Invoker:
def __init__(self):
self.commands=[]
def addCommand(self, cmd:Command):
self.commands.append(cmd)
def cancelCommand(self, cmd:Command):
self.commands.remove(cmd)
def invoke(self):
for cmd in self.commands:
cmd.execute()
Now is the time! 總指揮官! 請開始利用命令模式來執行兩階段的D-Day登陸作戰吧!
//準備海陸空軍
IReceiver navy = new ReceiverNavy();
IReceiver army = new ReceiverArmy();
IReceiver airForce = new ReceiverAirForce();
#region D-Day前:指揮官建立作戰計畫
//登陸作戰命令
Invoker invokerLanding = new Invoker();
Command[] commands4Landing = new Command[]{
new CmdBreakthrough(navy), //海軍突破
new CmdDefense(army), //陸軍
new CmdSupport(airForce) //空軍支援
};
commands4Landing.ToList().ForEach( cmd =>{
invokerLanding.AddCommand(cmd);
});
//登陸後作戰命令
Invoker invokerLanded = new Invoker();
Command[] commandsLanded = new Command[]{
new CmdBreakthrough(army), //陸軍突破
new CmdSupport(navy), //海軍支援
new CmdDefense(airForce) //空軍防禦
};
commandsLanded.ToList().ForEach( cmd =>{
invokerLanded.AddCommand(cmd);
});
#endregion
#region D-Day:開始執行作戰計畫
Trace.WriteLine("搶灘作戰開始!-----------------");
invokerLanding.Invoke();
var isEnemyTough = true;
if(isEnemyTough)//敵方砲火猛烈=>更新命令
{
//取消空軍支援
invokerLanded.CancelCommand(commandsLanded[2]);
//改加入空軍突破
invokerLanded.AddCommand(new CmdBreakthrough(airForce));
}
Trace.WriteLine("陸地作戰開始!-----------------");
invokerLanded.Invoke();
#endregion
# 準備海陸空軍
navy = ReceiverNavy()
army = ReceiverArmy()
airForce = ReceiverAirForce()
"""D-Day前:指揮官建立作戰計畫"""
# 登陸作戰命令
invokerLanding = Invoker()
commands4Landing = [
CmdBreakthrough(navy), #海軍突破
CmdDefense(army), #陸軍防禦
CmdSupport(airForce) #空軍支援
]
for cmd in commands4Landing:
invokerLanding.addCommand(cmd)
# 登陸後作戰命令
invokerLanded = Invoker()
commandsLanded = [
CmdBreakthrough(army), # 陸軍突破
CmdSupport(navy), # 海軍支援
CmdDefense(airForce) # 空軍防禦
]
for cmd in commandsLanded:
invokerLanded.addCommand(cmd)
"""D-Day:開始執行作戰計畫"""
print("搶灘作戰開始!-----------------")
invokerLanding.invoke()
isEnemyTough = True
if(isEnemyTough): #敵方砲火猛烈=>更新命令
# 取消空軍支援
invokerLanded.cancelCommand(commandsLanded[2])
# 改加入空軍突破
invokerLanded.addCommand(CmdBreakthrough(airForce))
print("陸地作戰開始!-----------------")
invokerLanded.invoke()
注意在主程式刻意模擬在搶灘成功後、陸地作戰前,更改了陸地作戰的命令。
執行作戰結果如下:
搶灘作戰開始!-----------------
[Navy] 集合艦艇!
[Navy] 射出所有魚雷和對空飛彈!
[Army] 不要跑到有沙的地方!
[Army] 子彈上膛!等待開火指令!
[AirForce] 以機槍掃射掩護!
陸地作戰開始!-----------------
[Army] 集合裝甲部隊和部兵!
[Army] 坦克及路面部隊開始前進突破敵方防線!
[Navy] 砲彈支援友軍!
[AirForce] 集合戰鬥機飛官!
[AirForce] 自由開火!
恭喜指揮官! 登陸作戰成功!
那麼何時適合用命令模式呢?
命令模式在Behavioral design patterns是其中一個相對複雜的模式,但是也是用來解釋Behavioral design patterns最好的模式;
它解除了高階模組與低階模組的耦合關係,讓兩者都依賴於抽象,大大的增加了程式碼的彈性與可擴充性!